package com.acme.exchange.domain;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Iterator;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;

import com.acme.exchange.domain.order.Order;
import com.acme.exchange.domain.order.OrderCompare;
import com.acme.exchange.network.proto.MessageConstructor;
import com.acme.exchange.network.proto.Protocol.ExchangeMessage;
import com.acme.exchange.network.proto.Protocol.ExchangeMessage.OrderStatus;
import com.acme.exchange.network.proto.Protocol.ExchangeMessage.OrderType;
import com.acme.exchange.network.proto.Protocol.ExchangeMessage.Side;

public class OpenBook {
	private TreeSet<Order> orderBookSell;
	private TreeSet<Order> orderBookBuy;
	final static BigDecimal PRICE_STEP = new BigDecimal(0.001);
	final static BigDecimal MAX_STOCK_PRICE = new BigDecimal(1000000000);
	final static BigDecimal MIN_STOCK_PRICE = new BigDecimal(0.001);
	private BigDecimal stockPrice;

	public OpenBook() {
		orderBookSell = new TreeSet<Order>(new OrderCompare());
		orderBookBuy = new TreeSet<Order>(new OrderCompare());
		stockPrice = new BigDecimal("30.0");

	}

	public void addNewOrder(Order order) {
		if (order.getOpType() == Side.BUY) {
			if (order.getOrderType() == OrderType.MARKET) {
				order.setPrice(MIN_STOCK_PRICE);
			}
			synchronized (orderBookBuy) {
				orderBookBuy.add(order);
			}
			return;
		}
		// operation.side == sell
		if (order.getOrderType() == OrderType.MARKET) {
			order.setPrice(MAX_STOCK_PRICE);
		}
		synchronized (orderBookSell) {
			orderBookSell.add(order);
		}
		return;
	}

	public boolean matchOrder(Order order,
			final Queue<ExchangeMessage> reportsForClients) {
		
		SortedSet<Order> fitSet;
		Order fitOrder;		
		if (order.getOpType() == Side.BUY) {

			synchronized (orderBookSell) {				
				if (orderBookSell.isEmpty()) {
					addNewOrder(order);
					return false;
				}

				if (order.getOrderType() == OrderType.MARKET) {

					fitSet = orderBookSell.headSet(orderBookSell.last(), true);

				} else {
					fitSet = orderBookSell.headSet(order, true);
					// return all sell orders with price better or equal to the
					// price of the current order
					// choose order with equal volume from fit
				}

				Iterator<Order> iter = fitSet.iterator();

				while (iter.hasNext()) {
					fitOrder = iter.next();		
					int newVolume = fitOrder.getVolume() - order.getVolume();

					if (newVolume > 0) {
						orderBookSell.remove(fitOrder); // remove order that matches
													// current order
						fitOrder.setVolume(newVolume);
						orderBookSell.add(fitOrder);
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), order,
								OrderStatus.FILLED, fitOrder, OrderStatus.PARTIALLY_FILLED);

						return true;
					}

					if (newVolume == 0) {

						orderBookSell.remove(fitOrder); // remove order that matches
						// current order
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), order,
								OrderStatus.FILLED, fitOrder, OrderStatus.FILLED);

						return true;
					}
					{// newVolume < 0, only Partially_filled						
						orderBookSell.remove(fitOrder); // remove order that matches			
						// current order
						order.setVolume(-newVolume);
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), fitOrder,
								OrderStatus.FILLED, order, OrderStatus.PARTIALLY_FILLED);

						return false;
					}
				}
			}

			// there is no matching anymore
			addNewOrder(order);
			return false;

		} else { // operationType = Sell

			synchronized (orderBookBuy) {				
				if (orderBookBuy.isEmpty()) {
					addNewOrder(order);
					return false;
				}

				if (order.getOrderType() == OrderType.MARKET) {

					fitSet = orderBookBuy.tailSet(orderBookBuy.first(), true);

				} else {
					fitSet = orderBookBuy.tailSet(order, true);
				}

				while (!fitSet.isEmpty()) {
					fitOrder = fitSet.last();

					int newVolume = fitOrder.getVolume() - order.getVolume();

					if (newVolume > 0) {
						orderBookBuy.remove(fitOrder); // remove order that matches
													// current order
						fitOrder.setVolume(newVolume);
						orderBookBuy.add(fitOrder);
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), order,
								OrderStatus.FILLED, fitOrder, OrderStatus.PARTIALLY_FILLED);

						return true;
					}

					if (newVolume == 0) {

						orderBookBuy.remove(fitOrder); // remove order that matches
						// current order
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), order,
								OrderStatus.FILLED, fitOrder, OrderStatus.FILLED);

						return true;
					}
					{// newVolume < 0, only Partially_filled						
						orderBookBuy.remove(fitOrder); // remove order that matches			
						// current order
						order.setVolume(-newVolume);
						stockPrice = fitOrder.getPrice();

						// generate a reports for both sides
						generateReports(reportsForClients, fitOrder.getPrice(), fitOrder,
								OrderStatus.FILLED, order, OrderStatus.PARTIALLY_FILLED);

						return false;
					}
				}
			}
			// there is no matching by volume
			addNewOrder(order);
			return false;
		}
	}

	private void generateReports(Queue<ExchangeMessage> reportsForClients,
			BigDecimal mPrice, Order order1, OrderStatus order1Status,
			Order order2, OrderStatus order2Status) {

		int volume;
		if (order1Status == OrderStatus.FILLED) {
			volume = order1.getVolume();
		} else {
			volume = order2.getVolume();
		}

		ExchangeMessage em = MessageConstructor.constructExecutionReport(
				order2, order2Status, mPrice, volume, order1.getClientLogin());

		reportsForClients.add(em);

		em = MessageConstructor.constructExecutionReport(order1, order1Status,
				mPrice, volume, order2.getClientLogin());

		reportsForClients.add(em);

	}

	public SortedSet<Order> getOrderBookSell() {
		return Collections.unmodifiableSortedSet(orderBookSell);
	}

	public SortedSet<Order> getOrderBookBuy() {
		return Collections.unmodifiableSortedSet(orderBookBuy);
	}

	public BigDecimal getStockPrice() {
		return stockPrice;
	}

}
